<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Markdown Viewer</title>
	<link rel="icon" href="static/favicon.ico" type="image/x-icon">
    <link rel="stylesheet" href="static/css/github-markdown.min.css">
    <link rel="stylesheet" href="static/css/github.min.css">
	<style>
		.enhance {
			position: absolute;
			top: 10px;
			right: 10px;
			color: #fff ;
			padding: 3px 6px;
			border-radius: 5px;
			font-size: 12px ;
			background: #343541de;
			opacity: 0.2;
			justify-content:space-between;
			cursor: pointer;
		}
		.enhance :hover{
			color: #bae9a4d7;
		}
		.markdown-body pre {
			width: 100%;
			line-height: 20px;
		}

		.code-container{
			display: flex;
			position: relative;
			width: 100%;
		}
		.left-num-wrapper {
			padding-top: 16px;
			margin-bottom: 16px;
			background-color: #f6f8fa;
		}
		.num-wrapper{
			counter-reset: code-num;
			display: flex;
			flex-direction: column;
			border-right: #d1d1d1 solid thin;
			width: 40px;
		}
		.num-item{
			position: relative;
			font-size: 14px;
			color: #d1d1d1;
			line-height: 20px;
			height: 20px;
		}
		.num-item:before{
			counter-increment: code-num;
			content: counter(code-num);
			position: absolute;
			right: 10px;
		}

		code .hljs-meta .hljs-string, code .hljs-regexp, code .hljs-string {
			color: #990230;
		}
	</style>
</head>
<body>
    <div id="app">
		<Row style="position:fixed;top:0;left:0;height:100vh;width:100vw;">
			<!-- 左侧列表 -->
			<i-col span="5" style="height:100vh;padding-top:0 20px;border-right:1px solid #888888">
				<!-- 已打开列表 -->
				<div style="padding:10px 0;height:25vh;overflow:auto;">
					<List v-if="current_unlock_note_id"  @click="handleGetNote(current_unlock_note_id)">
						<list-item >
							<div style="cursor: pointer;">
								<div>
									<i-button shape="circle" type="text" icon="ios-unlock-outline" @click.stop="handleFixedNote(current_unlock_note_id)"></i-button>
									<span v-if="current_unlock_note_id==current_note_id" style="color:rgb(45, 140, 240);margin-left:5px">{{ notes[current_unlock_note_id].title }}</span>
									<span v-if="current_unlock_note_id!=current_note_id" style="margin-left:5px">{{ notes[current_unlock_note_id].title }}</span>
								</div>
							</div>
						</list-item>
					</List>
					<List>
						<template v-for="id in fixed_notes">
							<list-item style="padding-top:5px;padding-bottom:5px;"  @click="handleGetNote(id)">
								<div style="cursor: pointer;">
									<div>
										<i-button shape="circle" type="text" icon="md-lock" @click.stop="handleUnFixedNote(id)"></i-button>
										<span v-if="id==current_note_id" style="color:rgb(45, 140, 240);margin-left:5px">{{ notes[id].title }}</span>
										<span v-if="id!=current_note_id" style="margin-left:5px">{{ notes[id].title }}</span>
									</div>
								</div>
							</list-item>
						</template>
					</List>
				</div>
				<!-- 笔记列表 -->
				<div style="height:75vh;padding-top:90px;overflow:auto;">
					<div style="position:relative;height:35px;padding-right:10px;">
						<i-button shape="circle" style="float:right;" @click="handleRefreshNotes">刷新</i-button>
					</div>
					<List style="padding-left:20px;padding-right:10px;">
						<template v-for="id in newest_notes">
							<list-item  @click="handleGetNote(id)"  style="cursor: pointer;">
								<div>
									<div>
										【最新】{{ notes[id].title }}
									</div>
								</div>
							</list-item>
						</template>
					</List>
					<i-menu  @on-select="handleGetNote" style="width:100%;">
						<template v-for="(list, dir) in dirs">
							<Submenu v-if="dir != '_'" :name="dir">
								<template  #title>
									<Icon type="ios-paper"></Icon>
									{{ dir }}
								</template>
								<template v-for="id in list" :key="id">
									<Menu-item :name="id">{{ notes[id].title }}</Menu-item>
								</template>
							</Submenu>
							<template v-if="dir == '_'" v-for="id in list" :key="id">
								<Menu-item :name="id">{{ notes[id].title }}</Menu-item>
							</template>
						</template>
					</i-menu>
				</div>
			</i-col>
			<!-- 内容页面 -->
			<i-col span="19" style="height:100vh;" >
				<template v-for="(page, i) in cache_page">
					<div :id="'cache_page_'+i" :ref="'cache_page_'+i" :style="{ display: this.current_note_id == page.id ? 'block' : 'none' }" class="markdown-body" style="overflow:auto;height:100vh;padding-right:260px;">	
						<div v-html="compileMarkdown(page.content)" style="padding:25px;border-right:1px solid #888;"></div>
						<!-- <div v-html="page.toc" style="position:fixed;top:20px;right:20px;padding:10px;border:1px solid #888;background-color: #fff;max-width:250px;"></div> -->
						<div style="position:fixed;top:20px;right:20px;padding:10px;width:250px;">
							<Anchor v-if="current_note_id" show-ink :container="$refs['cache_page_'+i][0]">
								<Anchor-Link v-for="item in page.toc" :href="'#'+item.id" :title="item.title" :style="item.style" />
							</Anchor>
						</div>
					</div>
				</template>
			</i-col>
		</Row>
		<!-- 搜索area -->
		<div style="width:20.83vw;position:fixed;top:25vh;left:0px;padding:15px 20px 0px 20px;max-height:75vh;z-index:10;background-color:#ffffff;border:1px solid #888;border-left:0;">
		    <!-- search input -->
			<div>
				<i-input v-model="search_text" search size="large" placeholder="Enter something..." style="display: inline-block;width:100%" @on-search="handleSearch" />
			</div>
			<!-- search list -->
			<div style="padding: 10px 0;">
				<div v-if="!is_searching && search_result.length > 0">
					<div style="position:relative;height:35px;">
						<i-button shape="circle" style="float:right;" @click="search_result=[];search_text='';search_message='';">清空</i-button>
					</div>
					<!-- search list -->
					<div style="max-height:50vh;overflow: auto;">
						<List>
							<template v-for="item in search_result">
							    <!-- search item -->
								<list-item  @click="handleGetNote(item.hash)" >
									<div style="cursor: pointer;">
										<div>
											<a href="#" v-html="item.title"></a>
										</div>
										<div v-html="item.best_paras.join('\n')"></div>
									</div>
								</list-item>
							</template>
						</List>
					</div>
				</div>
				<div v-if="search_message">
					{{ search_message }}
				</div>
			</div>
		</div>
    </div>

    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
	<!-- import Vue.js -->
	<script src="static/js/vue.global.js"></script>
	<!-- import stylesheet -->
	<link rel="stylesheet" href="static/css/viewuiplus.css">
	<!-- import View UI Plus -->
	<script src="static/js/viewuiplus.min.js"></script>
    <script src="static/js/highlight.min.js"></script>
    <script src="static/js/marked.min.js"></script>
	<script src="static/js/axios.min.js"></script>
	<script>
		MathJax = {
			tex: {inlineMath: [['$', '$'], ['\\(', '\\)']]}
		};
	</script>
	<script src="static/js/MathJax/tex-chtml.js"></script>
	<script src="static/js/marked_extension.js"></script>
    <script>
		marked.use({
			extensions: [{ // 代码高亮渲染
				name: 'code',
				renderer(token) {
					var lang = hljs.getLanguage(token.lang) ? token.lang : 'plaintext';
					var code_block = "<pre><code class=language-"+lang+" lang="+lang+">"+hljs.highlight(token.text,{language:lang}).value+"</code></pre>";
					code_block = enhanceCodeBlock(code_block, lang);
					code_block = addlineNumber(code_block);
					return code_block;
				}
			}]
		})
		marked.setOptions({
			renderer: new marked.Renderer(),
			gfm: true,
			pedantic: false,
			breaks: true
		});

		axios.defaults.timeout = 30000;
        const app = Vue.createApp({
			data() {
				url = new URL(document.URL);
				return {
					base_assets_url: url.origin + "/assets",
					base_api_url: url.origin + "/api",
					current_note_id: null,
					current_unlock_note_id: null,
					notes: {},
					dirs: {},
					newest_notes: [],
					search_text: "",
					search_result: [],
					is_searching: false,
					search_message: "",
					fixed_notes: [],
					cache_page: [ // 默认最多打开10个页面，这样写是为了防止页面重新渲染，导致滚动条位置变化
						{ id: "", content: "" },
						{ id: "", content: "" },
						{ id: "", content: "" },
						{ id: "", content: "" },
						{ id: "", content: "" },
						{ id: "", content: "" },
						{ id: "", content: "" },
						{ id: "", content: "" },
						{ id: "", content: "" },
						{ id: "", content: "" }
					]
				}
			},
            computed: {
            },
            methods: {
                compileMarkdown: function(content) {
                    return marked.marked(content);
                },
				handleRefreshNotes () {
					axios.get(this.base_api_url + "/refresh")
						.then(response => {
							var list = response.data;
							this.handleNotesData(list);
							this.$Message.success("刷新成功");
						})
						.catch(error => {
							this.$Message.error("刷新失败");
							console.error(error);
						});
				},
				cachePage(id, content) {
					for(var i = 0; i < this.cache_page.length; i++) {
						var page_id = this.cache_page[i].id;
						if(page_id == id) return; // 如果已经缓存过页面，则不再缓存
					}
					for(var i = 0; i < this.cache_page.length; i++) {
						var page_id = this.cache_page[i].id;
						if(page_id == "" || this.fixed_notes.indexOf(page_id) == -1) { // 固定note中不存在的id说明不再被使用了
							this.cache_page[i].id = id;
							this.cache_page[i].content = content;
							
							var page_container = this.$refs["cache_page_"+i][0];
							page_container.scrollTop = 0; // 信加载的得缓存页面，滚动条到最上面
							Vue.nextTick().then(() => { 
								// 等渲染完之后，建立目录
								this.cache_page[i].toc = getMarkdownHeading2(page_container);
								// 进行代码增强
								// vEnhanceCode(page_container, () => {
								// 	this.$Message.success("复制成功");
								// });
								// 2. 执行 mathjax 渲染
    							MathJax.typeset(); // 虽然不执行此语句已经可以解析数学表达式了，但是不显示，主要是字体不加载，所以执行一下。
							})
							break;
						}
					}
				},
				unCachePage(id) { // 应该不需要使用了
					for(var i = 0; i < this.cache_page.length; i++) {
						if(this.cache_page[i].id == id) {
							this.cache_page[i].id = "";
							this.cache_page[i].content = "";
							break;
						}
					}
				},
				handleFixedNote(id) {
					if(this.fixed_notes.length >= this.cache_page.length - 1) return; // 缓存页面太多就不让继续固定了
					var index = this.fixed_notes.indexOf(id);
					if(index == -1) {
						this.fixed_notes.unshift(id);
					}
					this.current_unlock_note_id = null; // 因为只有没锁的才会执行这个锁的函数，因此锁了之后就没有没被锁过的了
				},
				handleUnFixedNote(id) {
					var index = this.fixed_notes.indexOf(id);
					this.fixed_notes.splice(index, 1);
					if(this.current_note_id == id) {
						this.current_unlock_note_id = id;
					}
				},
				handleGetNote(id) {
					if(this.notes[id].content != null) { // 如果缓存里有就直接从缓存里拿
						if(this.fixed_notes.indexOf(id) == -1) {
							this.current_unlock_note_id = id;
						}
						this.current_note_id = id;
						this.cachePage(id, this.notes[id].content);
						return;
					}
					var md_path = this.notes[id].path;
					var folderPath = "/" + md_path.substring(0, md_path.lastIndexOf('/'));
					axios.get(this.base_api_url + "/note/" + id)
						.then(response => {
							var content = response.data.content;
							var matches = content.match(/\[.*?\]\((.*?\.assets.+?)\)/g);
							if(matches) {
								for(var i = 0; i < matches.length; i++) {
									var m = matches[i].split("](")[1];
									content = content.replace(m, this.base_assets_url + folderPath + "/" + m);
								}
							}
							this.notes[id].content = content; // 缓存起来，之后再看的话就不用去请求了
							if(this.fixed_notes.indexOf(id) == -1) {
								this.current_unlock_note_id = id;
							}
							this.current_note_id = id;
							this.cachePage(id, content);
						})
						.catch(error => {
							console.error(error);
						});
				},
				handleSearch() {
					var search_text = this.search_text;
					this.is_searching = true;
					this.search_message = "搜索中，等一会儿哈";
					axios.get(this.base_api_url + "/search?search_text=" + search_text)
						.then(response => {
							var list = response.data;
							for(var i = 0; i < list.length; i++) {
								var item = list[i];
								item.title = markRed(item.matched_keywords, item.title);
								item.best_paras = item.best_paras.map(i => markRed(item.matched_keywords, i));
							}
							console.log(list);
							this.search_result = list;
							this.is_searching = false;
							if(response.data.length == 0) this.search_message = "没有搜到任何结果";
							else this.search_message = "";
						})
						.catch(error => {
							console.error(error);
							this.is_searching = false;
							this.search_message = "出现了错误！";
						});
				},
				handleGetNotes() {
					axios.get(this.base_api_url + "/get_notes")
						.then(response => {
							var list = response.data;
							this.handleNotesData(list);
						})
						.catch(error => {
							console.error(error);
						});
				},
				handleNotesData(list) {
					var dic = {};
					var dirs = {};
					for(var i = 0; i < list.length; i++) {
						var item = list[i];
						var temp = item.path.split("/");
						var title = temp[temp.length - 1];
						var dir = temp.length > 1 ? temp[0] : "_";
						var note = {
							id: item.id,
							last_update_time: item.last_update_time,
							title: title,
							path: item.path,
							content: null
						};
						dic[item.id] = note;
						if(!dirs[dir]) dirs[dir] = []
						dirs[dir].push(note.id);
					}
					var num = 5;
					if(num > list.length) num = list.length;
					this.newest_notes = list.slice(0,num).map(e => e.id);
					this.notes = dic;
					this.dirs = dirs;
				},
			},
			created() {
				//this.handleGetNote("/必读：MD文件编写规范.md");
				this.handleGetNotes();
			}
        });
		app.use(ViewUIPlus);
		app.mount("#app");

		// 标记搜索内容，如果有关键字就标记为红色
		function markRed(keywords, content) {
			content = content.replace(/\s+/g, " "); // 空白字符全部改为空格，这时候里面就没有\t了
			for(var i = 0; i < keywords.length; i++) {
				var keyword = keywords[i];
				content = content.replaceAll(keyword, "\t" + keyword + "\t\t");
			}
			// 如果有连接的两个关键词，则会出现\t\t\t这种情况，合并两个关键词
			content = content.replaceAll("\t\t\t", "");
			content = htmlEncode(content); // 进行HTML编码，此时\t不会被编码
			content = content.replaceAll("\t\t", "</span>").replaceAll("\t", "<span style=\"color:red;\">");
			return content;
		}

		// 对内容进行html编码，使得content中的以html编码形式写的内容被识别为正常文字，不被转义。
		function htmlEncode(content) {
			var element = document.createElement('div');
			element.innerText = content;
			return element.innerHTML;
		}

		// 获取所有标题，返回html形式
		function getMarkdownHeading(element) {
			var heading_styles = {
				"h1": "font-size:20px;font-weight:bold;",
				"h2": "font-size:16px;font-weight:bold;padding-left:10px;",
				"h3": "font-size:14px;font-weight:bold;padding-left:20px;",
				"h4": "font-size:12px;font-weight:bold;padding-left:30px;color:#888;",
				"h5": "font-size:12px;font-weight:400;padding-left:40px;color:#888;",
				"h6": "font-size:12px;font-weight:100;padding-left:50px;color:#888;",
				
			};

			var headings = element.querySelectorAll('h1,h2,h3,h4,h5,h6');
			let toHtml = '';
			headings.forEach(heading => {
				var id = generateRandomString(16);
				heading.id = id;
				var tagName = heading.tagName.toLowerCase();
				var title = heading.innerText;
				toHtml+=`<li><a href="#${id}" style="${heading_styles[tagName]}">${title}</a></li>`;
			});

			return `<ul>${toHtml}</ul>`;
		}

		// 获取所有标题，返回json形式
		function getMarkdownHeading2(element) {
			var heading_styles = {
				"h1": "font-size:20px;font-weight:bold;padding-left:10px",
				"h2": "font-size:16px;font-weight:bold;padding-left:20px;",
				"h3": "font-size:14px;font-weight:bold;padding-left:30px;",
				"h4": "font-size:12px;font-weight:bold;padding-left:40px;color:#888;",
				"h5": "font-size:12px;font-weight:400;padding-left:50px;color:#888;",
				"h6": "font-size:12px;font-weight:100;padding-left:60px;color:#888;"
			};

			var headings = element.querySelectorAll('h1,h2,h3,h4,h5,h6');
			let result = [];
			headings.forEach(heading => {
				var id = generateRandomString(16);
				heading.id = id;
				var tagName = heading.tagName.toLowerCase();
				var title = heading.innerText;
				result.push({
					id: id,
					title: title,
					style: heading_styles[tagName]
				})
			});

			return result;
		}

		// 生成随机ID
		function generateRandomString(length) {
			return Math.random().toString(36).substr(2, length);
		}
    
		
		// 为代码添加行号
		function addlineNumber(content) {
			var len = content.split("\n").length;
			var numSpan = "<span class='num-item'></span>".repeat(len);
			var wrapper = `<div class="code-container"><div class="left-num-wrapper"><div class="num-wrapper">${numSpan}</div></div>${content}</div>`
			return wrapper;
		}

		//增强代码块，添加复制功能
		function enhanceCodeBlock(content, lang) {
			// console.log(content)
			//在pre块中增加一个元素用于显示
			let enhance = content.replace(/<pre><code/g, '<pre><div class="enhance" onclick="copyCodeToClip(this)"><div class="copy">'+lang+'</div></div><code')
			// console.log(enhance)
			return enhance;
		}

		/**
		 * 复制单行内容到粘贴板
		 * elem : 需要复制的元素
		 */
		function copyCodeToClip(elem) {
			var content = elem.parentElement.querySelector("code").innerText
			var aux = document.createElement("textarea");
			aux.value = content;
			document.body.appendChild(aux); 
			aux.select();
			document.execCommand("copy"); 
			document.body.removeChild(aux);
			app._context.components.Message.success("复制成功");
		}
	</script>
</body>
</html>
